<?php


namespace app\service\algorithm;

/**
 * php 实现 Apriori 算法
 * Class AprioriAT
 * @package app\service\algorithm
 */
class AprioriAT
{
    public $L;
    public $support;
    public function __construct($dataSet, $minSupport = 0.5)
    {
        $this->apriori($dataSet, $minSupport);
    }

    // 选取数据集的非重复元素组成候选集的集合C1
    public function createC1($dataSet)
    {
        $all = [];
        $C1 = [];
        foreach ($dataSet as $row) {
            foreach ($row as $col) {
                if (!in_array($col, $all)) {
                    $C1[] = [
                        $col
                    ];
                    $all[] = $col;
                }
            }
        }
        return $C1;
    }

    // 由Ck产生Lk：扫描数据集D，计算候选集Ck各元素在D中的支持度，选取支持度大于设定值的元素进入Lk
    public function scanD($dataSet, $Ck, $minSupport)
    {
        $countAll = count($dataSet);
        $countList = [];
        foreach ($Ck as $k => $c) {
            $countK = 0;
            foreach ($dataSet as $data) {
                $diff = array_diff($c, $data);
                if (count($diff) == 0) {
                    $countK += 1;
                }
            }
            $countList[$k] = $countK;
        }
        $Lk = [];
        $supportData = [];
        foreach ($countList as $k => $count) {
            $support = round($count / $countAll, 2);
            if ($support >= $minSupport) {
                $Lk[] = $Ck[$k];
                $supportData[] = $support;
            }
        }
        return [$Lk, $supportData];
    }

    // 由Lk产生Ck+1
    public function aprioriGen($Lk)
    {
        $Ck = [];
        for ($i = 0; $i < count($Lk); $i++) {
            $Li = array_slice($Lk[$i], 0, count($Lk[$i]) - 1);
            for ($j = $i + 1; $j < count($Lk); $j++) {
                $Lj = array_slice($Lk[$j], 0, count($Lk[$j]) - 1);
                if ($Lj == $Li) {
                    $Ck[] = array_unique(array_merge($Lk[$i], $Lk[$j]));
                }
            }
        }
        return $Ck;
    }

    // Apriori算法主函数
    public function apriori($dataSet, $minSupport)
    {
        $C1 = $this->createC1($dataSet);
        list($Lk, $supportData) = $this->scanD($dataSet, $C1, $minSupport);
        $L = [$Lk];
        $support = [$supportData];
        while (count($Lk) > 0) {
            $Ck = $this->aprioriGen($Lk);
            list($Lk, $supportData) = $this->scanD($dataSet, $Ck, $minSupport);
            if (count($Lk) > 0) {
                $L[] = $Lk;
                $support[] = $supportData;
            }
        }
        $this->L = $L;
        $this->support = $support;
        return [
            $L,
            $support
        ];
    }

    // 置信度计算函数
    public function calConf($A, $B)
    {
        $AB = array_merge($A, $B);
        $supAB = $this->getSup($AB);
//        print_r($supAB . "\n");
        $supA = $this->getSup($A);
//        print_r($supA . "\n");
        if ($supA == 0) {
            return 0;
        } else {
            return $supAB / $supA;
        }
    }

    public function getSup($set)
    {
        sort($set);
        foreach ($this->L as $i => $vi) {
            foreach ($vi as $j => $vj) {
                sort($vj);
                if ($set == $vj) {
                    return $this->support[$i][$j];
                }
            }
        }
        return 0;
    }
}
######## 使用方法 ##################
/**
 *
$data = array(
['牛奶', '面包'],
['面包', '尿布', '啤酒', '鸡蛋'],
['牛奶', '尿布', '啤酒', '可乐'],
['面包', '牛奶', '尿布', '啤酒'],
['面包', '牛奶', '尿布', '可乐']
);

$ap = new AprioriAT($data);
//print_r($ap->L);
$A = ['可乐'];
$B = ['面包'];
$conf = $ap->calConf($A, $B); // 计算 $A -> $B 的置信度
print_r($conf);
 */

